use crate::db::db_category;
use crate::db::entities::category::Model;
use crate::rest_api::api_category::{Category, CreateParams, SearchParams, UpdateParams};
use crate::rest_api::service::srv_subject::validate_subject_id;

use anyhow::Result;

use rbase::define::{ApiError, BaseError};
use rbase::id_utils::str2id;
use rbase_iam::db::define::SYSTEM_ID;
use rbase_iam::rest_api::api_user::UserToken;

pub async fn find_category_by_id(user_token: &UserToken, category_id: &String) -> Result<Category, ApiError> {
    let category_model = db_category::find_by_id_and_owner_ids(str2id(category_id)?, &user_token.owner_ids)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(category_id.to_string()))?;

    Ok(category_from_model(&category_model).await?)
}

pub async fn category_from_model(category_model: &Model) -> Result<Category> {
    let category = Category::from_model(category_model);
    Ok(category)
}

pub async fn delete_category_by_id(user_token: &UserToken, category_id: &String) -> Result<(), ApiError> {
    let category_model = db_category::find_by_id_and_owner_ids(str2id(category_id)?, &user_token.owner_ids)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(category_id.to_string()))?;
    // 普通用户不能修改系统资源
    if !user_token.is_admin && category_model.owner_id == Some(SYSTEM_ID) {
        return Err(BaseError::PermissionDenied.into());
    }
    db_category::delete_by_id(category_model.id).await?;
    Ok(())
}

pub async fn update_category_by_id(user_token: &UserToken, category_id: &String, new_category: &UpdateParams) -> Result<(), ApiError> {
    let subject_id = validate_subject_id(user_token, &new_category.subject_id).await?;
    let old_model = db_category::find_by_id_and_owner_ids(str2id(category_id)?, &user_token.owner_ids)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(category_id.to_string()))?;
    // 普通用户不能修改系统资源
    if !user_token.is_admin && old_model.owner_id == Some(SYSTEM_ID) {
        return Err(BaseError::PermissionDenied.into());
    }
    let new_model = Model {
        id: old_model.id,
        name: Some(new_category.name.clone()),
        subject_id,
        ..Default::default()
    };
    db_category::update(new_model).await?;
    Ok(())
}

pub async fn create_category(user_token: &UserToken, new_category: &CreateParams) -> Result<Category> {
    let subject_id = validate_subject_id(user_token, &new_category.subject_id).await?;

    let new_model = Model {
        name: Some(new_category.name.clone()),
        subject_id,
        owner_id: Some(user_token.id),
        ..Default::default()
    };
    let created_model = db_category::insert(new_model).await?;
    Ok(category_from_model(&created_model).await?)
}

pub async fn search(user_token: &UserToken, search_params: &SearchParams) -> Result<(Vec<Category>, i64)> {
    let (categories, total) = db_category::search(&db_category::SearchParams {
        name: search_params.name.clone(),
        subject_id: match &search_params.subject_id {
            Some(subject_id) => Some(str2id(&subject_id)?),
            None => None,
        },
        offset: search_params.offset,
        limit: search_params.limit,
        order_by: search_params.order_by.clone(),
        order: search_params.order.clone(),
        owner_ids: user_token.owner_ids.clone(),
    })
    .await?;

    let mut category_results = Vec::new();
    for category in categories {
        category_results.push(category_from_model(&category).await?);
    }

    Ok((category_results, total))
}

pub async fn validate_category_id(user_token: &UserToken, category_id: &Option<String>) -> Result<Option<i64>, ApiError> {
    match category_id {
        Some(category_id_str) => {
            let category_id = str2id(&category_id_str)?;
            db_category::find_by_id_and_owner_ids(category_id, &user_token.owner_ids)
                .await?
                .ok_or_else(|| BaseError::ResourceNotFound(category_id_str.to_string()))?;
            Ok(Some(category_id))
        }
        None => Ok(None),
    }
}
